﻿using Engine;
using Game;
using GameEntitySystem;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TemplatesDatabase;

namespace SCIENEW
{
	public class ComponentTrain : Component, IUpdateable
	{
		private static readonly Vector3 center = new Vector3(0.5f, 0, 0.5f);

		private static readonly Quaternion[] directions =
		{
			Quaternion.CreateFromAxisAngle(Vector3.UnitY, 0),
			Quaternion.CreateFromAxisAngle(Vector3.UnitY, MathUtils.PI * 0.5f),
			Quaternion.CreateFromAxisAngle(Vector3.UnitY, MathUtils.PI),
			Quaternion.CreateFromAxisAngle(Vector3.UnitY, MathUtils.PI * 1.5f)
		};

		private static readonly Quaternion upwardDirection = Quaternion.CreateFromAxisAngle(new Vector3(1, 0, 0), MathUtils.PI * 0.25f);
		private static readonly Quaternion downwardDirection = Quaternion.CreateFromAxisAngle(new Vector3(1, 0, 0), MathUtils.PI * -0.25f);

		private static readonly Vector3[] forwardVectors =
		{
			new Vector3(0, 0, -1),
			new Vector3(-1, 0, 0),
			new Vector3(0, 0, 1),
			new Vector3(1, 0, 0)
		};

		internal ComponentBody m_componentBody;
		public ComponentTrain ParentBody;
		protected ComponentDamage componentDamage;
		protected float m_outOfMountTime;
		private ComponentMount m_componentMount;
		public ComponentSteamEngineV ComponentEngine;
		private int m_forwardDirection;
		private Quaternion rotation;
		private Vector3 forwardVector;
        Vector3 m_previousPosition;
        Vector3 m_realSpeed;

		public int Direction
		{
			get { return m_forwardDirection; }
			set
			{
				forwardVector = forwardVectors[value];
				m_forwardDirection = value;
				rotation = directions[value];
			}
		}

		public UpdateOrder UpdateOrder => UpdateOrder.Default;
		public SubsystemBodies m_subsystemBodies;
		public SubsystemTerrain m_subsystemTerrain;

		public override void Load(ValuesDictionary valuesDictionary, IdToEntityMap idToEntityMap)
		{
			base.Load(valuesDictionary, idToEntityMap);
			m_subsystemBodies = Project.FindSubsystem<SubsystemBodies>(true);
			m_subsystemTerrain = Project.FindSubsystem<SubsystemTerrain>(true);
			m_componentBody = Entity.FindComponent<ComponentBody>(true);
			componentDamage = Entity.FindComponent<ComponentDamage>();
			m_componentMount = Entity.FindComponent<ComponentMount>(true);
			if ((ComponentEngine = Entity.FindComponent<ComponentSteamEngineV>()) != null)
				m_componentBody.CollidedWithBody += CollidedWithBody;
			else
				ParentBody = valuesDictionary.GetValue("ParentBody", default(EntityReference)).GetComponent<ComponentTrain>(Entity, idToEntityMap, false);

			Direction = valuesDictionary.GetValue("Direction", 0);
			m_forwardDirection = Direction;

            m_previousPosition = m_componentBody.Position;
        }

		public override void Save(ValuesDictionary valuesDictionary, EntityToIdMap entityToIdMap)
		{
			if (m_forwardDirection != 0)
				valuesDictionary.SetValue("Direction", m_forwardDirection);
			var value = EntityReference.FromId(ParentBody, entityToIdMap);
			if (!value.IsNullOrEmpty())
				valuesDictionary.SetValue("ParentBody", value);
		}

		public void CollidedWithBody(ComponentBody body)
		{
			if (Math.Abs(body.Density - 9.76f) <= float.Epsilon)
				return;
            if (m_componentBody.m_velocity.Length() < 0.5f) return;
			Vector2 v = m_realSpeed.XZ - body.Velocity.XZ;
			float amount = v.LengthSquared() * .3f;
			if (amount < .02f || m_realSpeed.XZ.LengthSquared() == 0f) return;
			var health = body.Entity.FindComponent<ComponentHealth>();
			if (health != null)
				health.Injure(amount / health.AttackResilience, null, false, "Struck by a train");
			else
				body.Entity.FindComponent<ComponentDamage>()?.Damage(amount);
			body.ApplyImpulse(Math.Clamp(1.25f * 6f * MathF.Pow(m_componentBody.Mass / body.Mass, 0.33f), 0f, 6f) * Vector3.Normalize(body.Position - m_componentBody.Position));
		}

		public ComponentTrain FindNearestTrain()
		{
			var bodies = new DynamicArray<ComponentBody>();
			m_subsystemBodies.FindBodiesAroundPoint(m_componentBody.Position.XZ, 2f, bodies);
			float num = 0f;
			ComponentTrain result = null;
			foreach (ComponentTrain train in bodies.Select(GetRailEntity))
			{
				if (train == null || train.Entity == Entity) continue;
				float score = 0f;
				const float maxDistance = 4f;
				if (train.m_componentBody.Velocity.LengthSquared() < 1f && train.Direction == Direction)
				{
					var v = train.m_componentBody.Position + Vector3.Transform(train.m_componentMount.MountOffset, train.m_componentBody.Rotation) - m_componentBody.Position;
					if (v.LengthSquared() <= maxDistance)
						score = maxDistance - v.LengthSquared();
				}
				if (score > num)
				{
					num = score;
					result = train;
				}
			}
			return result;
		}

		public void SetDirection(int value)
		{
			Direction = value;
			m_componentBody.Rotation = rotation;
		}

		public void Update(float dt)
		{
			//	ComponentEngineE.Coordinates = new Point3((int)m_componentBody.Position.X, (int)m_componentBody.Position.Y, (int)m_componentBody.Position.Z);
			if (m_componentMount.Rider != null)
			{
				var player = m_componentMount.Rider.Entity.FindComponent<ComponentPlayer>(true);
				player.ComponentLocomotion.LookOrder = player.ComponentInput.PlayerInput.Look;
			}
			switch (Direction)
			{
				case 0:
				case 2:
					m_componentBody.Position = new Vector3(MathF.Floor(m_componentBody.Position.X) + 0.5f, m_componentBody.Position.Y, m_componentBody.Position.Z);
					break;
				case 1:
				case 3:
					m_componentBody.Position = new Vector3(m_componentBody.Position.X, m_componentBody.Position.Y, MathF.Floor(m_componentBody.Position.Z) + 0.5f);
					break;
			}
			ComponentTrain t = this;
			int level = 0;
			for (; t.ParentBody != null; level++) t = t.ParentBody;
			if (level > 0)
			{
				var body = t.m_componentBody;
				var pos = body.Position;
				var r = body.Rotation;
				//if (ParentBody!=null)
				//m_componentBody.Rotation = Quaternion.Slerp(m_componentBody.Rotation, rotation, 0.715f);
				var Com = body.Entity.FindComponent<ComponentSteamEngineV>();
				float HLD = 0f;
				if (Com != null)
					HLD = Com.IsPowering() ? 100f : 1f;
				//HLD = Com2.Charged ? 200f : 1f;
				if (m_componentBody.StandingOnValue.HasValue && HLD >= 100f && body.m_velocity.Length() > 0f) //body.Velocity.LengthSquared() > 10f &&
																											  //Utils.SubsystemTime.QueueGameTimeDelayedExecution(Utils.SubsystemTime.GameTime + 0.23 * level, delegate
				{
					var result = m_subsystemTerrain.Raycast(m_componentBody.Position, m_componentBody.Position + new Vector3(0, -3f, 0), false, true, null);
					//result.Value.CellFace.Point
					if (result.HasValue && Terrain.ExtractContents(result.Value.Value) == RailBlock.Index && (dt *= SimulateRail(RailBlock.GetRailType(Terrain.ExtractData(result.Value.Value)))) > 0f)
					{
						//if (rotation.GetForwardVector().Y != 0 || ParentBody.m_componentBody.Position.Y != m_componentBody.Position.Y)
						//{
						//	m_componentBody.m_velocity += dt2 * rotation.GetForwardVector();
						//	m_componentBody.Rotation = Quaternion.Slerp(m_componentBody.Rotation, rotation, 0.15f);
						//	return;
						//}
						float ABS = 6f;
						if (HLD >= 200f)
							ABS = 30f;
						//	if (rotation.GetForwardVector().Y != 0f) { ABS = 3f; };
						m_componentBody.m_velocity = ABS * rotation.GetForwardVector();
						//m_componentBody.m_velocity.Z = ABS * rotation.GetForwardVector().Z;
						// m_componentBody.m_velocity.Y += rotation.GetForwardVector().Y * dt2/2;
						if (ParentBody != null && MathF.Abs(Vector3.Distance(ParentBody.m_componentBody.Position, m_componentBody.Position)) > 2f)
						{
							m_componentBody.m_velocity = ABS * rotation.GetForwardVector() * 1.4f;
							//m_componentBody.m_velocity.Z = ABS * rotation.GetForwardVector().Z * 1.4f;
						}
						//m_componentBody.m_velocity = ParentBody.m_componentBody.m_velocity.Length() * rotation.GetForwardVector() * 1.2f;
						if (ParentBody != null && MathF.Abs(Vector3.Distance(ParentBody.m_componentBody.Position, m_componentBody.Position)) < 1.2f)
						{
							m_componentBody.m_velocity = ABS * rotation.GetForwardVector() * 0.3f;
							//m_componentBody.m_velocity.Z = ABS * rotation.GetForwardVector().Z * 0.6f;
						}
						if (ParentBody != null && MathF.Abs(Vector3.Distance(ParentBody.m_componentBody.Position, m_componentBody.Position)) < 0.8f)
						{
							m_componentBody.m_velocity = ABS * rotation.GetForwardVector() * 0f;
							//m_componentBody.m_velocity.Z = ABS * rotation.GetForwardVector().Z * 0.6f;
						}
						m_componentBody.Rotation = Quaternion.Slerp(m_componentBody.Rotation, rotation, 0.715f);

						//m_componentBody.m_velocity = ParentBody.m_componentBody.m_velocity.Length() * rotation.GetForwardVector() * 0.8f;
					}
				}
				else if (Com != null && HLD < 100f && m_componentBody.m_velocity.Length() - 0.06f > 0f && m_componentBody.StandingOnValue.HasValue)
				{
					var result = m_subsystemTerrain.Raycast(m_componentBody.Position, m_componentBody.Position + new Vector3(0, -3f, 0), false, true, null) ?? default;
					SimulateRail(RailBlock.GetRailType(Terrain.ExtractData(result.Value)));
					m_componentBody.m_velocity = (m_componentBody.m_velocity.Length() - 0.06f) * rotation.GetForwardVector();
				}
				if (!m_componentBody.StandingOnValue.HasValue)
				{
					//rotation = Quaternion.CreateFromAxisAngle(Vector3.UnitY, 0);
					var result = m_subsystemTerrain.Raycast(m_componentBody.Position, m_componentBody.Position + new Vector3(0, -3f, 0), false, true, null) ?? default;
					SimulateRail(RailBlock.GetRailType(Terrain.ExtractData(result.Value)));
					m_componentBody.Rotation = rotation;
					m_componentBody.m_velocity -= new Vector3(0f, 40f, 0f) * dt;
					//m_componentBody.m_velocity -= new Vector3(0f,0.1f, 0f);
				}
				//m_componentBody.Rotation = Quaternion.Slerp(m_componentBody.Rotation, rotation, 0.715f);
				m_componentBody.Rotation = Quaternion.Slerp(m_componentBody.Rotation, rotation, 0.715f);
				m_outOfMountTime = MathF.Abs(Vector3.DistanceSquared(ParentBody.m_componentBody.Position, m_componentBody.Position)) > 24f
					? m_outOfMountTime + dt
					: 0f;
				ComponentDamage ComponentDamage = ParentBody.Entity.FindComponent<ComponentDamage>();
				if (m_outOfMountTime > 12f || (componentDamage != null && componentDamage.Hitpoints <= .05f) || ComponentDamage != null && ComponentDamage.Hitpoints <= .05f)
					ParentBody = null;
                {
                    UpdateSpeed(dt);
                    return;
                }
			}
			if (!m_componentBody.StandingOnValue.HasValue)
			{
				//rotation = Quaternion.CreateFromAxisAngle(Vector3.UnitY, 0);
				var result = m_subsystemTerrain.Raycast(m_componentBody.Position, m_componentBody.Position + new Vector3(0, -3f, 0), false, true, null) ?? default;
				SimulateRail(RailBlock.GetRailType(Terrain.ExtractData(result.Value)));
				m_componentBody.Rotation = rotation;
				m_componentBody.m_velocity -= new Vector3(0f, 40f, 0f) * dt;
				//m_componentBody.m_velocity -= new Vector3(0f, 0.1f, 0f);
			}
			var Com1 = m_componentBody.Entity.FindComponent<ComponentSteamEngineV>();
			float HLD1 = 0f;
			if (Com1 != null)
				HLD1 = Com1.IsPowering() ? 100f : 1f;

			if (HLD1 >= 100f && m_componentBody.StandingOnValue.HasValue) //
			{
				//time = 0;
				float ABS = 6f;
				if (HLD1 >= 200f)
					ABS = 30f;
				// if (rotation.GetForwardVector().Y != 0f) { ABS = 3f; };
				var result = m_subsystemTerrain.Raycast(m_componentBody.Position, m_componentBody.Position + new Vector3(0, -3f, 0), false, true, null) ?? default;
				if (Terrain.ExtractContents(result.Value) == RailBlock.Index && (dt *= SimulateRail(RailBlock.GetRailType(Terrain.ExtractData(result.Value)))) > 0f)
					m_componentBody.m_velocity = ABS * rotation.GetForwardVector();
			}
			else if (HLD1 < 100f && m_componentBody.m_velocity.Length() - 0.06f > 0f && m_componentBody.StandingOnValue.HasValue)
			{
				//float yyy = m_componentBody.m_velocity.Y;
				//time++;
				var result = m_subsystemTerrain.Raycast(m_componentBody.Position, m_componentBody.Position + new Vector3(0, -3f, 0), false, true, null) ?? default;
				SimulateRail(RailBlock.GetRailType(Terrain.ExtractData(result.Value)));
				m_componentBody.m_velocity = (m_componentBody.m_velocity.Length() - 0.06f) * rotation.GetForwardVector();
				//m_componentBody.m_velocity.Y = yyy;
			}
			m_componentBody.Rotation = Quaternion.Slerp(m_componentBody.Rotation, rotation, 0.715f);
            UpdateSpeed(dt);
		}

        void UpdateSpeed(float dt) {
            if (m_previousPosition != m_componentBody.Position) {
                Vector3 distance = new(MathF.Abs(m_componentBody.Position.X - m_previousPosition.X), MathF.Abs(m_componentBody.Position.Y - m_previousPosition.Y), MathF.Abs(m_componentBody.Position.Z - m_previousPosition.Z));
                m_realSpeed = distance / dt;
                m_previousPosition = m_componentBody.Position;
            }
        }

		//public int time=0;
		private float SimulateRail(int railType)
		{
			if (RailBlock.IsCorner(railType))
			{
				if (GetOffsetOnDirection(m_componentBody.Position, m_forwardDirection) > 0.5f)
					Turn(railType);
				return 50f;
			}
			if (RailBlock.IsDirectionX(railType) ^ !RailBlock.IsDirectionX(m_forwardDirection))
			{
				rotation = railType > 5
					? railType - 6 != Direction ? directions[Direction] * upwardDirection : directions[Direction] * downwardDirection
					: directions[Direction];
				return railType > 5 && railType - 6 != Direction ? 30f : 50f;
			}
			return 0f;
		}

		private bool Turn(int turnType)
		{
			if (Direction == turnType)
			{
				Direction = (Direction - 1) & 3;
				m_componentBody.Velocity = MathF.Abs(m_componentBody.Velocity.X + m_componentBody.Velocity.Z) * forwardVector;
				m_componentBody.Position = Vector3.Floor(m_componentBody.Position) + center;
				return true;
			}
			if (((Direction - 1) & 3) == turnType)
			{
				Direction = (Direction + 1) & 3;
				m_componentBody.Velocity = MathF.Abs(m_componentBody.Velocity.X + m_componentBody.Velocity.Z) * forwardVector;
				m_componentBody.Position = Vector3.Floor(m_componentBody.Position) + center;
				return true;
			}
			return false;
		}

		private static float GetOffsetOnDirection(Vector3 vec, int direction)
		{
			float offset = (direction & 1) == 0 ? vec.Z - MathF.Floor(vec.Z) : vec.X - MathF.Floor(vec.X);
			return (direction & 2) == 0 ? 1 - offset : offset;
		}

		public static ComponentTrain GetRailEntity(Component c) => c.Entity.FindComponent<ComponentTrain>();
	}
}
